package com.atguigu.dga.assess.service.impl;

import com.atguigu.dga.assess.assessor.AssessorTemplate;
import com.atguigu.dga.assess.bean.AssessParam;
import com.atguigu.dga.assess.bean.GovernanceAssessDetail;
import com.atguigu.dga.assess.bean.GovernanceMetric;
import com.atguigu.dga.assess.mapper.GovernanceAssessDetailMapper;
import com.atguigu.dga.assess.service.GovernanceAssessDetailService;
import com.atguigu.dga.assess.service.GovernanceMetricService;
import com.atguigu.dga.meta.bean.TableMetaInfo;
import com.atguigu.dga.meta.service.TableMetaInfoService;
import com.atguigu.dga.util.CacheUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.util.StopWatch;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * <p>
 * 治理考评结果明细 服务实现类
 * </p>
 *
 * @author atguigu
 * @since 2023-10-28
 */
@Service
public class GovernanceAssessDetailServiceImpl extends ServiceImpl<GovernanceAssessDetailMapper, GovernanceAssessDetail> implements GovernanceAssessDetailService {

    @Autowired
    private ThreadPoolTaskExecutor pool;
    @Autowired
    private TableMetaInfoService metaInfoService;

    @Autowired
    private GovernanceMetricService metricService;
    /*
        ①取得所有待考评表的列表 (List<TableMetaInfo>)
        ②取得所有待考评的指标项列表(List<GovernanceMetric>)
        ③计算得到各个表各个指标项的评分结果(List<GovernanceAssessDetail>)
        ④将考评结果写入数据库的governance_assess_detail保存起来
     */
    @Override
    public void generateAssessDetail(String db, String assessDate) throws ExecutionException, InterruptedException {
        //为了保证幂等性，先删除今天已经产生的考评结果
        remove(new QueryWrapper<GovernanceAssessDetail>().eq("assess_date",assessDate).eq("schema_name",db));
        //取得所有待考评表的列表 (List<TableMetaInfo>)
        List<TableMetaInfo> allTableMetaInfo = metaInfoService.getAllTableMetaInfo(db, assessDate);
        //把所有待考评表的元数据信息放入缓存即可
        saveTableMetaInfosToCache(allTableMetaInfo);
        //取得所有待考评的指标项列表(List<GovernanceMetric>)
        List<GovernanceMetric> metrics = metricService.list(new QueryWrapper<GovernanceMetric>().eq("is_disabled", "否"));
        //进行考评
        List<GovernanceAssessDetail> details = parallelAssess(allTableMetaInfo,metrics,assessDate);
        //存入数据库
        saveBatch(details);

    }

    @Autowired
    private CacheUtil cacheUtil;
    private void saveTableMetaInfosToCache(List<TableMetaInfo> allTableMetaInfo){
        //为了保证数据的实时更新 每次都是先清空
        Map<String, TableMetaInfo> map = cacheUtil.getTableMetaInfoMap();
        map.clear();
        //把查询到的最新的元数据信息放入map集合
        allTableMetaInfo
            .stream()
            .forEach(t -> map.put(
                cacheUtil.getKey(t),
                t
            ) );
    }

    @Autowired
    private ApplicationContext context;

    private List<GovernanceAssessDetail> parallelAssess(List<TableMetaInfo> allTableMetaInfo, List<GovernanceMetric> metrics, String assessDate) throws ExecutionException, InterruptedException {

        //专门用于收集线程任务执行完返回的结果
        CompletableFuture<List<GovernanceAssessDetail>>[] tasks = new CompletableFuture [allTableMetaInfo.size()];

       //提交任务
        for (int i = 0; i < allTableMetaInfo.size(); i++) {
            int finalI = i;
            tasks[i] = CompletableFuture.supplyAsync(new Supplier<List<GovernanceAssessDetail>>()
            {
                //和Callable的get是一样的，定义任务逻辑，返回数据
                @Override
                public List<GovernanceAssessDetail> get() {
                   //获取元数据信息
                    TableMetaInfo t = allTableMetaInfo.get(finalI);
                    //使用并行流考评
                    List<GovernanceAssessDetail> details = metrics.stream()
                                                                  .parallel()  //使用并行流
                                                                  .map(metric -> {
                                                                      //根据要计算的指标的名字，从容器中取出能考评这个指标的对应的考评器，赋值给模版父类类型
                                                                      AssessorTemplate assessor = context.getBean(metric.getMetricCode(), AssessorTemplate.class);
                                                                      GovernanceAssessDetail detail = assessor.doAssess(new AssessParam(t, metric, assessDate));
                                                                      return detail;
                                                                  })
                                                                  .collect(Collectors.toList());
                    return details;

                }
            }, pool);
        }

        //等待所有表的线程都考评完毕
        CompletableFuture.allOf(tasks).get();

        /*
            任务跑完，获取结果
                task1 ->  [ d1,d2,d3 ]
                task2 ->  [ d4,d5,d6 ]
                扁平化，把task1和task2合并，合并为 [d1,d2,d3,d4,d5,d6]
         */
        List<GovernanceAssessDetail> details = Arrays.stream(tasks)
                                                     .map(task -> {
                                                         try {
                                                             return task.get();  //[ d1,d2,d3 ]
                                                         } catch (Exception e) {
                                                             throw new RuntimeException(e);
                                                         }
                                                     })
                                                     .flatMap(c -> c.stream()) //扁平化
                                                     .collect(Collectors.toList());

        return details;
    }

    private List<GovernanceAssessDetail> assess(List<TableMetaInfo> allTableMetaInfo, List<GovernanceMetric> metrics, String assessDate) {

        List<GovernanceAssessDetail> details = new ArrayList<>();

        allTableMetaInfo
            .stream()
            .forEach(tableMetaInfo -> {
                /*
                    1.定义模版，在模版中把要执行的流程定义好
                    2.将流程中的关键步骤设置为抽象的，交给子类实现
                    3.使用模版对象执行流程
                 */
                for (GovernanceMetric metric : metrics) {
                    //根据要计算的指标的名字，从容器中取出能考评这个指标的对应的考评器，赋值给模版父类类型
                    AssessorTemplate assessor =  context.getBean(metric.getMetricCode(),AssessorTemplate.class);
                    GovernanceAssessDetail detail = assessor.doAssess(new AssessParam(tableMetaInfo, metric, assessDate));
                    details.add(detail);
                }
            });

        return details;
    }

    /**
     *
     * @param allTableMetaInfo  今天要考评的表的元数据信息
     * @param metrics       今天要考评的表的指标
     * @param assessDate   考评日期
     * @return   考评结果详情
     */
    private List<GovernanceAssessDetail> assessBak(List<TableMetaInfo> allTableMetaInfo, List<GovernanceMetric> metrics, String assessDate) {

        allTableMetaInfo
            .stream()
            .forEach(tableMetaInfo -> {
                /*
                    每一个指标都对这张表进行考评

                     使用if() 判断的方式，不优雅，违背了软件开发的开闭原则！

                     开闭原则：
                        ①增加新功能时，做到不修改旧代码
                        ②删除旧功能时，做到不修改当前的代码
                        增加或删除功能时，只对相关的业务功能代码进行操作，而不修改其他的代码！
                        维护不方便，不安全。

                     -------------------------
                        可以使用设计模式解决。

                        设计模式： 前辈们总结的针对不同的场景去解决不同的问题的套路！

                        模版模式。
                                把通用的流程在模版中定义好。
                                具体的实现，交给子类实现执行！

                        为什么模版模式可以解决这个问题：
                                不用模版： 具体的对象.doAssess()
                                使用模版:  模版父类.doAssess()
                                            父类在真正执行时，才会调用子类定义的流程。
                                            如果子类退役了，只需要解除子类和父类的关系即可。业务代码不变！
                 */
                for (GovernanceMetric metric : metrics) {
                    if ("HAVE_TEC_OWNER".equals(metric.getMetricCode())){
                        //new IfHadTecOwnner().doAssess(tableMetaInfo,metric);
                        // 模版.doAssess();
                    }else if ("HAVE_BUS_OWNER".equals(metric.getMetricCode())){
                        //new IfHadBusiOwnner().doAssess(tableMetaInfo,metric);
                        // 模版.doAssess();
                    }
                    //....写16个if判断
                    //....增加10个if判断
                }
            });

        return null;
    }
}
